// 智能指针
#include <memory>
#include <iostream>
#include <assert.h>

using namespace std;
/*
智能指针重载了 * -> 等操作符 用起来和原始指针一样

3种常用指针
1. unique_ptr
2. shared_ptr
3. weak_ptr

C++11的auto_ptr因为缺陷太多在C++17被彻底移除了
*/

class Obj {
    public:
    void foo()
    {
        cout << "foo!" << endl;
    }
    ~Obj()
    {
        cout << "destructor called!" << endl;
    }
};

int main()
{
    unique_ptr<Obj> ptr1(new Obj());
    assert(ptr1 != nullptr);
    ptr1->foo();
    unique_ptr<string> ptr2(new string("hello"));
    assert(*ptr2 == "hello");

    //虽然unique_ptr叫指针用起来也像指针但实际上并不是指针而是一个对象所以不要试图对unique_ptr调用delete，它会自己在离开作用域时释放资源
    //另外unique_ptr没有定义加减运算 不能随意移动指针地址
    //unique_ptr必须初始化使用 不能不初始化直接当成普通变量使用unique_ptr<int> ptr;这种是不正确的 未初始化的unique_ptr相当于空指针
    //所以推荐使用C++14标准中的make_unique工厂函数来创建智能指针并且强制初始化 同时还可以使用auto自动推导
    auto ptr3 = make_unique<int>(42);

    //使用unique_ptr注意指针的所有权问题 unique_ptr的所有权是唯一的不允许共享 unique_ptr应用了C++的转移语义实现这个功能 同时禁止了复制赋值
    //因此在向另一个unique_ptr赋值的时候要特别留意需要使用函数std::move()显式声明所有权的转移 赋值操作后指针所有权被转移原来的unique_ptr变成了
    //空指针 新的unique_ptr接替了所有权保证了所有权的唯一性
    assert(ptr3 && *ptr3 == 42);

    auto ptr4 = std::move(ptr3);
    assert(!ptr3 && ptr4);  //这里ptr3变成了空指针

    //尽量不要对unique_ptr进行赋值操作
    //另外unique_ptr因为不允许赋值只能转移 所以如果在定义类时将unique_ptr作为成员 那么类本身也会是不可复制的 也就是说unique_ptr会把它的唯一所有权特性转递给他的持有者

    //shared_ptr基本用法与另外unique_ptr一致 最大不同点是它的所有权可以安全共享 支持复制赋值允许多人同时持有
    shared_ptr<int> ptr5(new int(10));
    assert(*ptr5 == 10);
    auto ptr6 = make_shared<int>(42); //工厂函数创建
    assert(*ptr6 == 42);
    assert(ptr6.use_count() == 1);
    auto ptr7 = ptr6;
    assert(ptr6.use_count() == 2);

    //shared_ptr使用成本比unique_ptr大 使用场景复杂以后 很难知道shared_ptr真正的释放时机 如果这个对象的析构函数中有非常复杂或者阻塞的操作
    //那么就会在一个不确定的事件点释放资源造成进程或者线程阻塞

    //shared_ptr会造成循环引用问题

    class Node final
    {
        using this_type = Node;
        using shared_type = std::shared_ptr<this_type>;
        public:
        shared_type next;
    };
    auto n1 = make_shared<Node>();
    auto n2 = make_shared<Node>();
    assert(n1.use_count() == 1);
    n1->next = n2;
    n2->next = n1;
    assert(n1.use_count() == 2);

    class Node1 final
    {
        public:
        weak_ptr<Node1> next; //将make_shared替换为weak_ptr解决循环引用问题 weak_ptr只会【观察】指针不会修改引用计数
    };
    auto n3 = make_shared<Node1>();
    auto n4 = make_shared<Node1>();
    assert(n3.use_count() == 1);
    n3->next = n4;
    n4->next = n3;
    assert(n3.use_count() == 1);

    if (!n3->next.expired()) //检查weak_ptr是否有效
    {
        auto ptr = n3->next.lock(); //调用lock把弱引用转为强引用 获取shared_ptr
        assert(ptr == n4);
    }

    //weak_ptr重要用途就是让类正确自我创建shared_ptr对象内部使用weak_ptr来保管this指针然后调用lock获取shared_ptr
    //这里用到辅助类enable_shared_from_this 需要自我管理的类以继承的方式使用它 之后就可以用成员函数shared_from_this创建shared_ptr
    //或者调用weak_from_this创建weak_ptr
    class SharedSelf final : public enable_shared_from_this<SharedSelf>{};

    auto ss = make_shared<SharedSelf>();
    assert(ss.use_count() == 1);
    auto ss1 = ss->shared_from_this();
    assert(ss.use_count() == 2);
    auto ss2 = ss->weak_from_this();
    assert(!ss2.expired() && ss.use_count() == 2);
}

/*
如果指针是单独使用 就使用unique_ptr
如果是共享使用 就选择shared_ptr
如果可能存在循环引用或者不需要shared_ptr那样的强引用关系那就应该选择weak_ptr 既不影响计数也可以随时转成shared_ptr
应该调用工厂方法make_unique和make_shared来创建智能指针强制初始化还能用auto简化声明
有的资料建议不要再函数入口参数使用shared_ptr 原因是成本高，这里建议先放手去用 功能正确后再优化
*/